package com.air.thread;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

/**
 * @author shent
 * LockSupport类使用了一种名为Permit（许可）的概念来做到阻塞和唤醒线程的功能，
 * 可以把许可看成是一种(0,1)信号量（Semaphore），
 * 但与 Semaphore 不同的是，许可的累加上限是1。
 * 初始时，permit为0，当调用unpark()方法时，线程的permit加1，
 * 当调用park()方法时，如果permit为0，则调用线程进入阻塞状态。
 */
public class LockSupportDemo {
    /**
     * 假设现在需要实现一种FIFO类型的独占锁，
     * 可以把这种锁看成是ReentrantLock的公平锁简单版本，
     * 且是不可重入的，就是说当一个线程获得锁后，其它等待线程以FIFO的调度方式等待获取锁。
     */
    static class FIFOMutex {
        private final AtomicBoolean locked = new AtomicBoolean(false);
        private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();

        public void lock() {
            Thread current = Thread.currentThread();
            waiters.add(current);

            // 如果当前线程不在队首，或锁已被占用，则当前线程阻塞
            // NOTE：这个判断的意图其实就是：锁必须由队首元素拿到
            while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
                LockSupport.park(this);
            }
            waiters.remove(); // 删除队首元素
        }

        public void unlock() {
            locked.set(false);
            LockSupport.unpark(waiters.peek());
        }
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {
        FIFOMutex mutex = new FIFOMutex();
        MyThread a1 = new MyThread("a1", mutex);
        MyThread a2 = new MyThread("a2", mutex);
        MyThread a3 = new MyThread("a3", mutex);

        a1.start();
        a2.start();
        a3.start();

        a1.join();
        a2.join();
        a3.join();

        assert MyThread.count == 300;
        System.out.print("Finished");
    }
}


class MyThread extends Thread {
    private String name;
    private LockSupportDemo.FIFOMutex mutex;
    public static int count;


    public MyThread(String name, LockSupportDemo.FIFOMutex mutex) {
        this.name = name;
        this.mutex = mutex;
    }


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            mutex.lock();
            count++;
            System.out.println("name:" + name + "  count:" + count);
            mutex.unlock();
        }
    }
}
